using UnityEngine;

namespace GameToolkit
{
    public interface IAnimator
    {
        void SetTrigger(int id);
        void ResetTrigger(int id);
        void SetBool(int id, bool value);
        void SetFloat(int id, float value);
        void SetInteger(int id, int value);
    }

    [System.AttributeUsage(System.AttributeTargets.Field, AllowMultiple = false)]
    public class AnimParamTypeAttribute : PropertyAttribute
    {
        public AnimatorControllerParameterType ParameterType { get; set; }
        public AnimParamTypeAttribute(AnimatorControllerParameterType type)
        {
            ParameterType = type;
        }
    }


    [System.Serializable]
    public struct AnimParam : System.IEquatable<AnimParam>
    {
        //[HideInInspector]
        [SerializeField]
        string _name;

        //[HideInInspector]
        [SerializeField]
        int _id;

        //[HideInInspector]
        public AnimatorControllerParameterType type;

        //[HideInInspector]
        [SerializeField]
        float _value;

        [System.NonSerialized]
        bool _valid;
        [System.NonSerialized]
        int _validId;
        public bool IsValid { get { return _valid; } }

        public string name
        {
            get { return _name; }
            set
            {
                _name = value;
                _id = string.IsNullOrEmpty(value) ? 0 : Animator.StringToHash(value);
            }
        }
        public int id { get { return _id; } }

        public int intValue { get { return (int)_value; } set { _value = value; } }
        public float floatValue { get { return _value; } set { _value = value; } }
        public bool boolValue { get { return _value > 0.2f; } set { _value = (value ? 1 : 0); } }

        public bool Validate(ActorBehaviour actor, bool validateFromBuffer = true)
        {
            if (_id != 0 && actor != null)
            {
                var id = actor.GetInstanceID();
                if (_validId == id)
                    return _valid;
                _valid = actor.HasParameter(this);
                _validId = id;
#if UNITY_EDITOR
                if (!_valid && !validateFromBuffer)
                    Debug.LogWarningFormat(actor, "AnimParam(type:{0}, name:{1}) is invalid for \"{2}\"", type, _name, actor.name);
#endif
            }
            else
            {
                _valid = false;
                _validId = 0;
            }
            return _valid;
        }

        public bool Validate(Animator animator, bool validateFromBuffer = true)
        {
            if (_id != 0 && animator != null)
            {
                var id = animator.GetInstanceID();
                if (validateFromBuffer && _validId == id)
                    return _valid;
                _valid = false;
                _validId = id;
                for (int i = 0; i < animator.parameterCount; i++)
                {
                    var p = animator.GetParameter(i);
                    if (p.nameHash == _id && p.type == type)
                    {
                        _valid = true;
                        break;
                    }
                }
#if UNITY_EDITOR
                if (!_valid && !validateFromBuffer)
                    Debug.LogWarningFormat(animator, "AnimParam(type:{0}, name:{1}) is invalid for \"{2}\"", type, _name, animator.name);
#endif
            }
            else
            {
                _valid = false;
                _validId = 0;
            }
            return _valid;
        }

        public void ApplyTrigger(Animator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetTrigger(_id);
        }

        public void ApplyTrigger(IAnimator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetTrigger(_id);
        }

        public void ApplyFloat(Animator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetFloat(_id, _value);
        }
        public void ApplyFloat(IAnimator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetFloat(_id, _value);
        }

        public void ApplyBool(Animator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetBool(_id, boolValue);
        }

        public void ApplyBool(IAnimator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetBool(_id, boolValue);
        }

        public void ApplyInt(Animator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetInteger(_id, intValue);
        }
        public void ApplyInt(IAnimator anim)
        {
            if (_id != 0 && anim != null)
                anim.SetInteger(_id, intValue);
        }

        public void Apply(Animator anim)
        {
            if (anim == null || _id == 0)
                return;
            if (type == AnimatorControllerParameterType.Trigger)
                anim.SetTrigger(_id);
            else if (type == AnimatorControllerParameterType.Float)
                anim.SetFloat(_id, floatValue);
            else if (type == AnimatorControllerParameterType.Bool)
                anim.SetBool(_id, boolValue);
            else if (type == AnimatorControllerParameterType.Int)
                anim.SetInteger(_id, intValue);
        }

        public void Apply(IAnimator anim)
        {
            if (anim == null || _id == 0)
                return;
            anim.SetFloat(_id, _value);
        }

        public void Clear(Animator anim)
        {
            if (anim == null || _id == 0)
                return;
            if (type == AnimatorControllerParameterType.Trigger)
                anim.ResetTrigger(_id);
        }
        public void Clear(IAnimator anim)
        {
            if (anim == null || _id == 0)
                return;
            if (type == AnimatorControllerParameterType.Trigger)
                anim.ResetTrigger(_id);
        }

        public override string ToString()
        {
            if(string.IsNullOrEmpty(_name))
            {
                return "invalid param";
            }
            switch(type)
            {
                case AnimatorControllerParameterType.Bool:
                    return $"{_name}({boolValue})";
                case AnimatorControllerParameterType.Float:
                    return $"{_name}({floatValue})";
                case AnimatorControllerParameterType.Int:
                    return $"{_name}({intValue})";
                case AnimatorControllerParameterType.Trigger:
                    return $"{_name}(trigger)";
                default:
                    return "invalid param";
            }
        }

        public static AnimParam ReferenceTo(AnimatorControllerParameter aparam)
        {
            AnimParam p;
            p.type = aparam.type;
            p._name = aparam.name;
            p._id = aparam.nameHash;
            p._valid = false;
            p._validId = 0;
            if (p.type == AnimatorControllerParameterType.Bool || p.type == AnimatorControllerParameterType.Trigger)
                p._value = aparam.defaultBool ? 1 : 0;
            else if (p.type == AnimatorControllerParameterType.Int)
                p._value = aparam.defaultInt;
            else if (p.type == AnimatorControllerParameterType.Float)
                p._value = aparam.defaultFloat;
            else
                p._value = 0;
            return p;
        }

        public static AnimParam ReferenceTo(string name, AnimatorControllerParameterType type)
        {
            AnimParam p;
            p.type = type;
            p._name = name;
            p._id = name == null ? 0 : Animator.StringToHash(name);
            p._value = 0;
            p._valid = false;
            p._validId = 0;
            return p;
        }

        public bool Equals(AnimParam other)
        {
            return _id == other._id && type == other.type && _value == other._value;
        }

        public static implicit operator int(AnimParam aparam)
        {
            return aparam._id;
        }

        public static explicit operator string(AnimParam aparam)
        {
            return aparam._name;
        }
    }
}
